package com.stone.encrypt.aspect;

import com.stone.encrypt.annotation.Encrypt;
import com.stone.encrypt.app.service.EncryptService;
import com.stone.encrypt.infra.enums.EncryptType;
import com.stone.encrypt.infra.util.EncryptionUtils;
import com.stone.starter.core.constant.BaseConstants;
import com.stone.starter.core.exception.EncryptionException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * 加密切面
 *
 * @author Mr_wenpan@163.com 2021/08/02 21:39
 */
@Slf4j
@Aspect
@Component
public class EncryptAspectHandler implements ApplicationContextAware {

    /**
     * 用于SpEL表达式解析.
     */
    private static final SpelExpressionParser PARSER = new SpelExpressionParser();
    /**
     * 用于获取方法参数定义名字.
     */
    private static final DefaultParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    /**
     * 加密解密服务类
     */
    private EncryptService encryptService;

    @Pointcut("@annotation(com.stone.encrypt.annotation.Encrypt)")
    public void encrypt() {
    }

    @SneakyThrows
    @Around(value = "encrypt()")
    public Object arround(ProceedingJoinPoint joinPoint) {
        // 加密异常必须抛出
        handleEncrypt(joinPoint);
        return joinPoint.proceed();
    }

    private void handleEncrypt(ProceedingJoinPoint joinPoint) {

        // 获取到方法上的加密注解
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Encrypt annotation = method.getAnnotation(Encrypt.class);
        // 获取需要加密的对象属性
        String[] encryptFields = annotation.encryptFields();

        if (ArrayUtils.isNotEmpty(encryptFields)) {
            // 通过joinPoint获取被注解方法的形参
            Object[] args = joinPoint.getArgs();
            // 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
            String[] paramNames = NAME_DISCOVERER.getParameterNames(method);

            // 如果方法没有参数则直接返回
            if (Objects.isNull(args) || Objects.isNull(paramNames)) {
                return;
            }

            // spring的表达式上下文对象
            EvaluationContext context = new StandardEvaluationContext();
            // 给上下文赋值，解析每一个参数名 + 参数值到上下文中
            for (int i = 0; i < args.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }

            // 遍历每一个需要加密的对象属性
            for (String field : encryptFields) {
                if (!field.startsWith(BaseConstants.Symbol.WELL)) {
                    field = BaseConstants.Symbol.WELL.concat(field);
                }
                // 解析过后的Spring表达式对象
                Expression expression = PARSER.parseExpression(field);
                // 表达式从上下文中计算出实际参数值
                String expressionValue = expression.getValue(context, String.class);
                // 空值默认不加密
                if (Objects.nonNull(expressionValue)) {
                    String prefix = annotation.prefix();
                    String encryptResult = executeEncrypt(expressionValue, annotation);
                    // 拼接用户自定义加密字符串后的前缀
                    if (StringUtils.isNotBlank(prefix) && StringUtils.isNotBlank(encryptResult)) {
                        // 拼接前缀并赋值到原属性
                        encryptResult = prefix.concat(encryptResult);
                    }
                    PARSER.parseExpression(field).setValue(context, encryptResult);
                    continue;
                }
                log.warn("传递的加密字段解析表达式【 {} 】为空，请检查需要加密的字段是否设置正确.", field);
            }

        }

    }

    /**
     * 执行加密
     *
     * @param fieldValue 加密的属性值
     * @param annotation 加密注解
     * @return java.lang.String
     * @author Mr_wenpan@163.com 2021/8/3 11:47 上午
     */
    private String executeEncrypt(String fieldValue, Encrypt annotation) {
        String result = null;
        EncryptType encryptType = annotation.encryptType();

        switch (encryptType) {
            case AES:
                // securityKey接口用户没有实现，则无法进行RSA加密（AES加密必须要securityKey）
                if (Objects.isNull(encryptService)) {
                    throw new EncryptionException("加密异常，因为无法获取到securityKey，无法使用AES加密，请检查securityKey获取接口实现.");
                }
                result = EncryptionUtils.AES.encrypt(fieldValue, encryptService.getSecurityKey());
                break;
            case MD5:
                // MD5不加盐加密（非对称加密）
                result = EncryptionUtils.MD5.encrypt(fieldValue);
                break;
            case RSA:
                // 公钥接口用户没有实现，则无法进行RSA加密（RSA加密必须要公钥）
                if (Objects.isNull(encryptService)) {
                    throw new EncryptionException("加密异常，因为无法获取到公钥，无法使用RSA加密，请检查公钥获取接口实现.");
                }
                // 获取加密公钥（私钥加密暂不实现）
                String publicKey = encryptService.getPublicKey();
                String salt = annotation.salt();
                if (annotation.withSalt()) {
                    // 公钥加盐加密（对称加密）
                    result = EncryptionUtils.RSA.encrypt(fieldValue, salt, publicKey);
                } else {
                    // 公钥不加盐加密（对称加密）
                    result = EncryptionUtils.RSA.encrypt(fieldValue, publicKey);
                }
                break;
            case RSA2:
                // 暂时不实现
                throw new EncryptionException("抱歉！RSA2加密算法暂未实现，请更换其他算法进行加密.");
            default:
                break;
        }

        return result;
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        try {
            encryptService = applicationContext.getBean(EncryptService.class);
        } catch (Exception ex) {
            log.warn("没有EncryptService实现类，将无法使用需要获取秘钥 / 公钥的加密方式.");
        }
    }
}
